using System;
using System.Collections.Generic;

namespace ArithemeticSolver
{
	internal class Equation : IValue
	{
		private readonly List<Element> m_elements = new List<Element>();

		public Equation(Queue<string> strings)
		{
			while (true)
			{
				if (strings.Count == 0) break;

				var elementString = strings.Dequeue();
				ElementType type = ElementTypeHelper.ToElementType(elementString);

				if (type == ElementType.Operator)
				{
					m_elements.Add(new Operator(elementString));
				}
				else if (type == ElementType.Operand)
				{
					m_elements.Add(new Operand(elementString));
				}
				else if (type == ElementType.LeftParenthesis) 
				{
					m_elements.Add(new Equation(strings));
				}
				else if (type == ElementType.RightParenthesis)
				{
					break;
				}
			}
		}

		public float Value
		{
			get { return Evaluate(); }
		}

		private bool HasMoreOperator()
		{
			return m_elements.Count > 1;
		}

		private Operator GetTopMostOperator(out int index)
		{
			Operator topOperator = null;
			index = 0;

			for (int i = m_elements.Count - 1; i >= 0; i--)
			{
				if (m_elements[i] is Operator == false) continue;

				Operator op = (Operator)m_elements[i];
				if (op.IsSameOrHigherPriorityThan(topOperator))
				{
					topOperator = op;
					index = i;
				}
			}

			return topOperator;
		}

		private IValue GetValue(int index)
		{
			return (IValue)m_elements[index];
		}

		private void ReplaceResult(int operatorIndex, IValue result)
		{
			int leftOperatorIndex = operatorIndex - 1;
			m_elements.RemoveAt(leftOperatorIndex);
			m_elements.RemoveAt(leftOperatorIndex);
			m_elements.RemoveAt(leftOperatorIndex);

			m_elements.Insert(leftOperatorIndex, result);
		}

		private float Evaluate()
		{
			while (HasMoreOperator())
			{
				int topOperatorIndex;
				Operator topOperator = GetTopMostOperator(out topOperatorIndex);

				var left = GetValue(topOperatorIndex - 1);
				var right = GetValue(topOperatorIndex + 1);
				var result = topOperator.Evaluate(left, right);

				ReplaceResult(topOperatorIndex, result);
			}

			return GetFinalResult();
		}

		private float GetFinalResult()
		{
			return ((Operand)m_elements[0]).Value;
		}
	}
}